﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;

namespace InvoiceInvoker.MvcUI.Models
{

	#region Models
	[PropertiesMustMatch("NewPassword", "ConfirmPassword", ErrorMessage = "Nowe hasło i jego porwierdzenie nie są zgodne.")]
	public class ChangePasswordModel
	{
		[Required(ErrorMessage="Podaj aktualne hasło")]
		[DataType(DataType.Password)]
		[DisplayName("Aktualne hasło")]
		public string OldPassword { get; set; }

		[Required(ErrorMessage = "Podaj nowe hasło")]
		[ValidatePasswordLength]
		[DataType(DataType.Password)]
		[DisplayName("Nowe hasło")]
		public string NewPassword { get; set; }

		[Required(ErrorMessage = "Potwierdź nowe hasło")]
		[DataType(DataType.Password)]
		[DisplayName("Potwierdzenie nowego hasła")]
		public string ConfirmPassword { get; set; }
	}

	public class LogOnModel
	{
		[Required(ErrorMessage = "Podaj nazwę użytkownika")]
		[DisplayName("Nazwa użytkownika")]
		[StringLength(50, ErrorMessage = "Nazwa użytkownika może mieć co najwyżej 50 znaków")]
		public string UserName { get; set; }

		[Required(ErrorMessage = "Podaj hasło")]
		[DataType(DataType.Password)]
		[DisplayName("Hasło")]
		public string Password { get; set; }

		[DisplayName("Pamiętaj mnie")]
		public bool RememberMe { get; set; }
	}

	[PropertiesMustMatch("Password", "ConfirmPassword", ErrorMessage = "Hasło i jego potwierdzenie nie są zgodne.")]
	public class RegisterModel
	{
		[Required(ErrorMessage = "Podaj nazwę użytkownika")]
		[DisplayName("Nazwa użytkownika")]
		[StringLength(50, ErrorMessage = "Nazwa użytkownika może mieć co najwyżej 50 znaków")]
		public string UserName { get; set; }

		[Required(ErrorMessage = "Podaj adres e-mail")]
		[DataType(DataType.EmailAddress)]
		[DisplayName("E-mail")]
		public string Email { get; set; }

		[Required(ErrorMessage = "Podaj hasło")]
		[ValidatePasswordLength]
		[DataType(DataType.Password)]
		[DisplayName("Hasło")]
		public string Password { get; set; }

		[Required(ErrorMessage = "Potwierdź hasło")]
		[DataType(DataType.Password)]
		[DisplayName("Potwierdzenie hasła")]
		public string ConfirmPassword { get; set; }
	}
	#endregion

	#region Services
	// The FormsAuthentication type is sealed and contains static members, so it is difficult to
	// unit test code that calls its members. The interface and helper class below demonstrate
	// how to create an abstract wrapper around such a type in order to make the AccountController
	// code unit testable.

	public interface IMembershipService
	{
		int MinPasswordLength { get; }

		bool ValidateUser(string userName, string password);
		MembershipCreateStatus CreateUser(string userName, string password, string email);
		bool ChangePassword(string userName, string oldPassword, string newPassword);
	}

	public class AccountMembershipService : IMembershipService
	{
		private readonly MembershipProvider _provider;

		public AccountMembershipService()
			: this(null)
		{
		}

		public AccountMembershipService(MembershipProvider provider)
		{
			_provider = provider ?? Membership.Provider;
		}

		public int MinPasswordLength
		{
			get
			{
				return _provider.MinRequiredPasswordLength;
			}
		}

		public bool ValidateUser(string userName, string password)
		{
			if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");
			if (String.IsNullOrEmpty(password)) throw new ArgumentException("Value cannot be null or empty.", "password");

			return _provider.ValidateUser(userName, password);
		}

		public MembershipCreateStatus CreateUser(string userName, string password, string email)
		{
			if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");
			if (String.IsNullOrEmpty(password)) throw new ArgumentException("Value cannot be null or empty.", "password");
			if (String.IsNullOrEmpty(email)) throw new ArgumentException("Value cannot be null or empty.", "email");

			MembershipCreateStatus status;
			_provider.CreateUser(userName, password, email, null, null, true, null, out status);
			return status;
		}

		public bool ChangePassword(string userName, string oldPassword, string newPassword)
		{
			if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");
			if (String.IsNullOrEmpty(oldPassword)) throw new ArgumentException("Value cannot be null or empty.", "oldPassword");
			if (String.IsNullOrEmpty(newPassword)) throw new ArgumentException("Value cannot be null or empty.", "newPassword");

			// The underlying ChangePassword() will throw an exception rather
			// than return false in certain failure scenarios.
			try
			{
				MembershipUser currentUser = _provider.GetUser(userName, true /* userIsOnline */);
				return currentUser.ChangePassword(oldPassword, newPassword);
			}
			catch (ArgumentException)
			{
				return false;
			}
			catch (MembershipPasswordException)
			{
				return false;
			}
		}
	}

	public interface IFormsAuthenticationService
	{
		void SignIn(string userName, bool createPersistentCookie);
		void SignOut();
	}

	public class FormsAuthenticationService : IFormsAuthenticationService
	{
		public void SignIn(string userName, bool createPersistentCookie)
		{
			if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");

			FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
		}

		public void SignOut()
		{
			FormsAuthentication.SignOut();
		}
	}
	#endregion

	#region Validation
	public static class AccountValidation
	{
		public static string ErrorCodeToString(MembershipCreateStatus createStatus)
		{
			// See http://go.microsoft.com/fwlink/?LinkID=177550 for
			// a full list of status codes.
			switch (createStatus)
			{
				case MembershipCreateStatus.DuplicateUserName:
					return "Ta nazwa użytkownika jest już zajęta.";

				case MembershipCreateStatus.DuplicateEmail:
					return "Ten adres e-mail jest już używany.";

				case MembershipCreateStatus.InvalidPassword:
					return "Podane hasło jest niewłaściwe.";

				case MembershipCreateStatus.InvalidEmail:
					return "Podany adres e-mail jest niewłaściwy.";

				case MembershipCreateStatus.InvalidAnswer:
					return "Odpowiedź na pytanie jest niepoprawna.";

				case MembershipCreateStatus.InvalidQuestion:
					return "Pytanie jest niepoprawne.";

				case MembershipCreateStatus.InvalidUserName:
					return "Nazwa użytkownika jest niewłaściwa.";

				case MembershipCreateStatus.ProviderError:
					return "The authentication provider returned an error. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

				case MembershipCreateStatus.UserRejected:
					return "The user creation request has been canceled. Please verify your entry and try again. If the problem persists, please contact your system administrator.";

				default:
					return "An unknown error occurred. Please verify your entry and try again. If the problem persists, please contact your system administrator.";
			}
		}
	}

	[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
	public sealed class PropertiesMustMatchAttribute : ValidationAttribute
	{
		private const string _defaultErrorMessage = "'{0}' i '{1}' nie są zgodne.";
		private readonly object _typeId = new object();

		public PropertiesMustMatchAttribute(string originalProperty, string confirmProperty)
			: base(_defaultErrorMessage)
		{
			OriginalProperty = originalProperty;
			ConfirmProperty = confirmProperty;
		}

		public string ConfirmProperty { get; private set; }
		public string OriginalProperty { get; private set; }

		public override object TypeId
		{
			get
			{
				return _typeId;
			}
		}

		public override string FormatErrorMessage(string name)
		{
			return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
				OriginalProperty, ConfirmProperty);
		}

		public override bool IsValid(object value)
		{
			PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
			object originalValue = properties.Find(OriginalProperty, true /* ignoreCase */).GetValue(value);
			object confirmValue = properties.Find(ConfirmProperty, true /* ignoreCase */).GetValue(value);
			return Object.Equals(originalValue, confirmValue);
		}
	}

	[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
	public sealed class ValidatePasswordLengthAttribute : ValidationAttribute
	{
		private const string _defaultErrorMessage = "{0} musi mieć co najmniej {1} znaków.";
		private readonly int _minCharacters = Membership.Provider.MinRequiredPasswordLength;

		public ValidatePasswordLengthAttribute()
			: base(_defaultErrorMessage)
		{
		}

		public override string FormatErrorMessage(string name)
		{
			return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
				name, _minCharacters);
		}

		public override bool IsValid(object value)
		{
			string valueAsString = value as string;
			return (valueAsString != null && valueAsString.Length >= _minCharacters);
		}
	}
	#endregion

}
